import * as Cesium from "cesium";
import Sandcastle from "Sandcastle";

const viewer = new Cesium.Viewer("cesiumContainer", {
  terrain: Cesium.Terrain.fromWorldTerrain({
    requestVertexNormals: true, //Needed to visualize slope
  }),
});

function getElevationContourMaterial() {
  // Creates a composite material with both elevation shading and contour lines
  return new Cesium.Material({
    fabric: {
      type: "ElevationColorContour",
      materials: {
        contourMaterial: {
          type: "ElevationContour",
        },
        elevationRampMaterial: {
          type: "ElevationRamp",
        },
      },
      components: {
        diffuse:
          "contourMaterial.alpha == 0.0 ? elevationRampMaterial.diffuse : contourMaterial.diffuse",
        alpha: "max(contourMaterial.alpha, elevationRampMaterial.alpha)",
      },
    },
    translucent: false,
  });
}

function getSlopeContourMaterial() {
  // Creates a composite material with both slope shading and contour lines
  return new Cesium.Material({
    fabric: {
      type: "SlopeColorContour",
      materials: {
        contourMaterial: {
          type: "ElevationContour",
        },
        slopeRampMaterial: {
          type: "SlopeRamp",
        },
      },
      components: {
        diffuse:
          "contourMaterial.alpha == 0.0 ? slopeRampMaterial.diffuse : contourMaterial.diffuse",
        alpha: "max(contourMaterial.alpha, slopeRampMaterial.alpha)",
      },
    },
    translucent: false,
  });
}

function getAspectContourMaterial() {
  // Creates a composite material with both aspect shading and contour lines
  return new Cesium.Material({
    fabric: {
      type: "AspectColorContour",
      materials: {
        contourMaterial: {
          type: "ElevationContour",
        },
        aspectRampMaterial: {
          type: "AspectRamp",
        },
      },
      components: {
        diffuse:
          "contourMaterial.alpha == 0.0 ? aspectRampMaterial.diffuse : contourMaterial.diffuse",
        alpha: "max(contourMaterial.alpha, aspectRampMaterial.alpha)",
      },
    },
    translucent: false,
  });
}

const elevationRamp = [0.0, 0.045, 0.1, 0.15, 0.37, 0.54, 1.0];
const slopeRamp = [0.0, 0.29, 0.5, Math.sqrt(2) / 2, 0.87, 0.91, 1.0];
const aspectRamp = [0.0, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0];
function getColorRamp(selectedShading) {
  const ramp = document.createElement("canvas");
  ramp.width = 100;
  ramp.height = 1;
  const ctx = ramp.getContext("2d");

  let values;
  if (selectedShading === "elevation") {
    values = elevationRamp;
  } else if (selectedShading === "slope") {
    values = slopeRamp;
  } else if (selectedShading === "aspect") {
    values = aspectRamp;
  }

  const grd = ctx.createLinearGradient(0, 0, 100, 0);
  grd.addColorStop(values[0], "#000000"); //black
  grd.addColorStop(values[1], "#2747E0"); //blue
  grd.addColorStop(values[2], "#D33B7D"); //pink
  grd.addColorStop(values[3], "#D33038"); //red
  grd.addColorStop(values[4], "#FF9742"); //orange
  grd.addColorStop(values[5], "#ffd700"); //yellow
  grd.addColorStop(values[6], "#ffffff"); //white

  ctx.fillStyle = grd;
  ctx.fillRect(0, 0, 100, 1);

  return ramp;
}

const minHeight = -414.0; // approximate dead sea elevation
const maxHeight = 8777.0; // approximate everest elevation
const contourColor = Cesium.Color.RED.clone();
let contourUniforms = {};
let shadingUniforms = {};

// The viewModel tracks the state of our mini application.
const viewModel = {
  enableContour: false,
  contourSpacing: 150.0,
  contourWidth: 2.0,
  selectedShading: "elevation",
  changeColor: function () {
    contourUniforms.color = Cesium.Color.fromRandom(
      { alpha: 1.0 },
      contourColor,
    );
  },
};

// Convert the viewModel members into knockout observables.
Cesium.knockout.track(viewModel);

// Bind the viewModel to the DOM elements of the UI that call for it.
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(viewModel, toolbar);

function updateMaterial() {
  const hasContour = viewModel.enableContour;
  const selectedShading = viewModel.selectedShading;
  const globe = viewer.scene.globe;
  let material;
  if (hasContour) {
    if (selectedShading === "elevation") {
      material = getElevationContourMaterial();
      shadingUniforms = material.materials.elevationRampMaterial.uniforms;
      shadingUniforms.minimumHeight = minHeight;
      shadingUniforms.maximumHeight = maxHeight;
      contourUniforms = material.materials.contourMaterial.uniforms;
    } else if (selectedShading === "slope") {
      material = getSlopeContourMaterial();
      shadingUniforms = material.materials.slopeRampMaterial.uniforms;
      contourUniforms = material.materials.contourMaterial.uniforms;
    } else if (selectedShading === "aspect") {
      material = getAspectContourMaterial();
      shadingUniforms = material.materials.aspectRampMaterial.uniforms;
      contourUniforms = material.materials.contourMaterial.uniforms;
    } else {
      material = Cesium.Material.fromType("ElevationContour");
      contourUniforms = material.uniforms;
    }
    contourUniforms.width = viewModel.contourWidth;
    contourUniforms.spacing = viewModel.contourSpacing;
    contourUniforms.color = contourColor;
  } else if (selectedShading === "elevation") {
    material = Cesium.Material.fromType("ElevationRamp");
    shadingUniforms = material.uniforms;
    shadingUniforms.minimumHeight = minHeight;
    shadingUniforms.maximumHeight = maxHeight;
  } else if (selectedShading === "slope") {
    material = Cesium.Material.fromType("SlopeRamp");
    shadingUniforms = material.uniforms;
  } else if (selectedShading === "aspect") {
    material = Cesium.Material.fromType("AspectRamp");
    shadingUniforms = material.uniforms;
  }
  if (selectedShading !== "none") {
    shadingUniforms.image = getColorRamp(selectedShading);
  }

  globe.material = material;
}

updateMaterial();

Cesium.knockout
  .getObservable(viewModel, "enableContour")
  .subscribe(function (newValue) {
    updateMaterial();
  });

Cesium.knockout
  .getObservable(viewModel, "contourWidth")
  .subscribe(function (newValue) {
    contourUniforms.width = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "contourSpacing")
  .subscribe(function (newValue) {
    contourUniforms.spacing = parseFloat(newValue);
  });

Cesium.knockout
  .getObservable(viewModel, "selectedShading")
  .subscribe(function (value) {
    updateMaterial();
  });

Sandcastle.addToolbarMenu(
  [
    {
      text: "Himalayas",
      onselect: function () {
        viewer.camera.setView({
          destination: new Cesium.Cartesian3(
            322100.7492728492,
            5917960.047024654,
            3077602.646977297,
          ),
          orientation: {
            heading: 5.988151498702285,
            pitch: -1.5614542839414822,
            roll: 0,
          },
        });
        viewer.clockViewModel.currentTime = Cesium.JulianDate.fromIso8601(
          "2017-09-22T04:00:00Z",
        );
      },
    },
    {
      text: "Half Dome",
      onselect: function () {
        viewer.camera.setView({
          destination: new Cesium.Cartesian3(
            -2495709.521843174,
            -4391600.804712465,
            3884463.7192916023,
          ),
          orientation: {
            heading: 1.7183056487769202,
            pitch: -0.06460370548034144,
            roll: 0.0079181631783527,
          },
        });
        viewer.clockViewModel.currentTime = Cesium.JulianDate.fromIso8601(
          "2017-09-22T18:00:00Z",
        );
      },
    },
    {
      text: "Vancouver",
      onselect: function () {
        viewer.camera.setView({
          destination: new Cesium.Cartesian3(
            -2301222.367751603,
            -3485269.915771613,
            4812080.961755785,
          ),
          orientation: {
            heading: 0.11355958593902571,
            pitch: -0.260011078090858,
            roll: 0.00039019018274721873,
          },
        });
        viewer.clockViewModel.currentTime = Cesium.JulianDate.fromIso8601(
          "2017-09-22T18:00:00Z",
        );
      },
    },
    {
      text: "Mount Everest",
      onselect: function () {
        viewer.camera.setView({
          destination: new Cesium.Cartesian3(
            282157.6960889096,
            5638892.465594703,
            2978736.186473513,
          ),
          orientation: {
            heading: 4.747266966349747,
            pitch: -0.2206998858596192,
            roll: 6.280340554587955,
          },
        });
        viewer.clockViewModel.currentTime = Cesium.JulianDate.fromIso8601(
          "2017-09-22T04:00:00Z",
        );
      },
    },
  ],
  "zoomButtons",
);
